﻿using System;

using System.Collections.Generic;
using System.Text;
using System.Drawing;
using Silvermoon.Animations;
using System.Diagnostics;
using System.Threading;
using Silvermoon.Controls;
using Silvermoon.Controls.Interfaces;
using Silvermoon.Controls.Classes;
using Silvermoon.Controls.Base;

namespace Silvermoon.Core
{
    /// <summary>
    /// Creates and coordinates messages between controls.
    /// </summary>
    public class MessageServer : IDisposable
    {
        #region constants

        /// <summary>
        /// Specifies how long it takes until a context menu opens up while the mouse is still pressed.
        /// </summary>
        public const int ContextMenuDelay = 750;

        /// <summary>
        /// If a control is clicked twice within this period, the first click is not recognized as single click:
        /// </summary>
        public const int MaxDoubleClickPeriod = 200;

        /// <summary>
        /// Unless the mouse is not dragged more than this threshold in pixels, ScrollEvents are not raised:
        /// </summary>
        public const int DragThreshold = 60;

        /// <summary>
        /// Specifies the default duration while ScrollEventArgs are raised.
        /// </summary>
        public const int DefaultScrollDuration = 2500;

        /// <summary>
        /// Specifies the minumum distance in pixels that must exceed to recognize a scroll gesture:
        /// </summary>
        public const int ScrollDetectionDurationThreshold = 400;

        /// <summary>
        /// Specifies the minimum speed in pixel per milliseconds that must exceed to recognize a scroll gesture:
        /// </summary>
        public const double ScrollDetectionSpeedThreshold = 0.10;

        /// <summary>
        /// Secifies the maximum duration how long a control can be hold and released to recognize it as click. if this duration expires, no OnClickEventArgs are raised:
        /// </summary>
        public const int ClickExpiredDuration = 950;

        #endregion
        #region ctor/tdor

        /// <summary>
        /// Initializes a new instance of the <see cref="MessageEngine"/> class.
        /// </summary>
        /// <param name="host">The host control for rendering.</param>
        /// <param name="inputControl">The control that has the input focus.</param>
        internal MessageServer(System.Windows.Forms.Control host, System.Windows.Forms.Control inputControl)
            : base()
        {
            this.Host = host;
            Screen = Screen.Current;

            host.MouseDown += new System.Windows.Forms.MouseEventHandler(OnMouseDown);
            host.MouseUp += new System.Windows.Forms.MouseEventHandler(OnMouseUp);
            host.MouseMove += new System.Windows.Forms.MouseEventHandler(OnMouseMove);
            host.KeyDown += new System.Windows.Forms.KeyEventHandler(OnKeyDown);
            host.KeyUp += new System.Windows.Forms.KeyEventHandler(OnKeyUp);
            host.KeyPress += new System.Windows.Forms.KeyPressEventHandler(OnKeyPress);

            if (inputControl != host)
            {
                inputControl.KeyDown += new System.Windows.Forms.KeyEventHandler(OnKeyDown);
                inputControl.KeyUp += new System.Windows.Forms.KeyEventHandler(OnKeyUp);
                inputControl.KeyPress += new System.Windows.Forms.KeyPressEventHandler(OnKeyPress);
            }


            beginScrollEventArgs.Position = currentPosition;
            beginScrollEventArgs.StartPosition = startPosition;
        }


        public void Dispose()
        {
            StopSingleClickTimer();
            Host.MouseDown -= OnMouseDown;
            Host.MouseUp -= OnMouseUp;
            Host.MouseMove -= OnMouseMove;
            Host.KeyDown -= OnKeyDown;
            Host.KeyUp -= OnKeyUp;
            Host.KeyPress -= OnKeyPress;
        }

        #endregion
        #region Fields

        private Control toolTipControl;
        private System.Threading.Timer singleClickExpirationTimer;
        private Control inputFocusedControl;
        private Point startPosition = new Point();
        private Point currentPosition = new Point();
        private Point moveDeltaPos = new Point();
        private int mouseDownTime;
        private int distance;
        private bool ignoreMouseMoves = false;
        private bool ignoreMouseClick = false;
        private bool isDraggingOrScrolling = false;
        private bool isVirtualMouseDown;
        private bool isMouseDown;
        private int lastDuration;
        private int lastMoveTick;
        private int clickCount;
        private int contextMenuStartTime;
        private ScrollData scrollData = new ScrollData();
        private Control mouseFocusControl;

        /// <summary>
        /// for performance isues, whe do not create an EventArg for every event, but use always the same:
        /// </summary>
        private MouseDownEventArgs mouseDownEventArgs = new MouseDownEventArgs();
        private MouseUpEventArgs mouseUpEventArgs = new MouseUpEventArgs();
        private MouseMoveEventArgs mouseMoveEventArgs = new MouseMoveEventArgs();
        private ScrollEventArgs scrollEventArgs = new ScrollEventArgs();
        private MouseClickEventArgs mouseClickEventArgs = new MouseClickEventArgs();
        private BeginScrollEventArgs beginScrollEventArgs = new BeginScrollEventArgs();
        private EndScrollEventArgs endScrollEventArgs = new EndScrollEventArgs();
        private List<Control> mouseParentStack = new List<Control>();
        private List<Control> focusParentStack = new List<Control>();
        private List<Rectangle> controlSize = new List<Rectangle>();


        #endregion
        #region Properties

        protected Control ContextMenuControl { get; set; }

        public Screen Screen { get; private set; }

        public System.Windows.Forms.Control Host { get; private set; }

        public bool IsMouseDown { get { return isMouseDown; } }

        /// <summary>
        /// Gets or sets the root controls to send messages.
        /// </summary>
        public IList<Control> Controls { get; set; }

        /// <summary>
        /// Gets or sets the control that has the keyboard focus.
        /// </summary>
        public Control MouseFocusControl
        {
            get { return mouseFocusControl; }
            set { if (mouseFocusControl != value) { mouseFocusControl = value; OnMouseFocusChanged(); } }
        }

        /// <summary>
        /// Gets or sets the control that is the target of a mouse down.
        /// </summary>
        public Control InputFocusControl
        {
            get { return inputFocusedControl; }
            set
            {
                if (inputFocusedControl != value)
                {
                    OnInputFocusChanged(inputFocusedControl, value);
                    IMouseEventBehavior ctrl = inputFocusedControl as IMouseEventBehavior;
                    if (ctrl != null) ctrl.OnLostFocus();
                    inputFocusedControl = value;
                    ctrl = inputFocusedControl as IMouseEventBehavior;
                    if (ctrl != null) ctrl.OnGotFocus();

                }
            }
        }


        #endregion
        #region Mouse Methods

        delegate void Action();

        public void StartSingleClickTimer()
        {
            StopSingleClickTimer();
            singleClickExpirationTimer = new System.Threading.Timer((state) =>
                {
                    StopSingleClickTimer();
                    Host.Invoke(new Action(() =>
                    {
                        MouseUpEventArgs ev = this.mouseUpEventArgs;
                        clickCount = 0;
                        RaiseMouseEvent(ev, (c, evt) => RaiseClickEvent(c, evt, MouseClickType.SingleClick));
                    }));

                }, null, MaxDoubleClickPeriod, System.Threading.Timeout.Infinite);
        }

        public void StopSingleClickTimer()
        {
            if (singleClickExpirationTimer != null) singleClickExpirationTimer.Dispose();
            singleClickExpirationTimer = null;
        }


        private void OnMouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {
                UpdateStatistics(e, true);

                // only raise MouseMoveEventArgs when a MouseDown has been detected:
                if (isVirtualMouseDown && !isDraggingOrScrolling) RaiseMouseMoveEvent();

                // raise ScrollEvents even if no MouseDown is detected, to allow scrolling even when the last MouseDown
                // event has caused to abort a running scroll:
                DetermineScrollOrBegingScrollEvents();
            }
        }

        /// <summary>
        /// Raise MouseMoveEventArgs to all controls.
        /// </summary>
        private void RaiseMouseMoveEvent()
        {
            if (!ignoreMouseMoves)
            {
                MouseMoveEventArgs ev = mouseMoveEventArgs;
                ev.Handled = false;
                ev.Cancel = false;
                ev.StartPosition = startPosition;
                ev.Position = currentPosition;
                RaiseMouseEvent(ev, (c, evt) => c.OnMouseMove(evt));
                if (ev.Cancel) ignoreMouseMoves = true;
            }
        }

        private void RaiseScrollEvents()
        {
            HideToolTip();
            ScrollEventArgs ev = scrollEventArgs;
            if (ev.Cancel) return;

            ev.IsMouseDown = System.Windows.Forms.Control.MouseButtons == System.Windows.Forms.MouseButtons.Left;
            ev.Handled = false;
            ev.StartPosition = startPosition;
            ev.Position = currentPosition;
            RaiseScrollEvent(ev, (c, evt) => c.OnScroll(ev));
            if (ev.Cancel) RaiseEndScrollEvent();
        }

        private void DetermineAndRaiseBeginScrollEvents()
        {

            int distance = GetOriginDeltaDistance();
            if (distance > DragThreshold)
            {
                RemoveContextMenuDelay();
                BeginScrollEventArgs ev = beginScrollEventArgs;
                DetermineScrollDirection(ev);
                ev.Duration = DefaultScrollDuration;
                ev.Handled = false;
                ev.Cancel = false;
                ev.Position = currentPosition;
                ev.StartPosition = startPosition;
                RaiseScrollEvent(ev, (c, evt) => c.OnBeginScrollData(ev));
                if (ev.Cancel || !ev.Handled)
                {
                    scrollEventArgs.Cancel = true;
                    clickCount = 0;
                }
                if (!ev.Cancel && !isDraggingOrScrolling)
                {
                    RaiseMouseUpEvent(false);
                    isVirtualMouseDown = false;
                }
                isDraggingOrScrolling = !(ev.Cancel || !ev.Handled);
                ev.Cancel = false;
            }
        }

        private void DetermineScrollDirection(BeginScrollEventArgs ev)
        {
            int dx = currentPosition.X - startPosition.X;
            int dy = currentPosition.Y - startPosition.Y;
            int ax = Math.Abs(dx);
            int ay = Math.Abs(dy);
            if (ax > ay)
            {
                ev.ScrollDirection = dx > 0 ? ScrollDirection.Right : ScrollDirection.Left;
            }
            else
            {
                ev.ScrollDirection = dy > 0 ? ScrollDirection.Down : ScrollDirection.Up;
            }
        }

        private void DetermineScrollOrBegingScrollEvents()
        {
            if (isDraggingOrScrolling)
            {
                // dragging has already been detected:
                RaiseScrollEvents();
            }
            else
            {
                // check if the minimum distance has exceeded to recognize dragging:
                DetermineAndRaiseBeginScrollEvents();
            }
        }

        /// <summary>
        /// Raise EndScrollEventArgs to notify control(s) that scrolling is completed.
        /// </summary>
        void RaiseEndScrollEvent()
        {
            if (isDraggingOrScrolling)
            {
                EndScrollEventArgs ev = endScrollEventArgs;
                ev.Handled = false;
                RaiseScrollEvent(ev, (c, evt) => c.OnEndScrollData(ev));
                isDraggingOrScrolling = false;
            }
        }

        private Point GetLastDeltaMove()
        {
            return new Point(currentPosition.X - moveDeltaPos.X, currentPosition.Y - moveDeltaPos.Y);
        }

        private Point GetOriginDelta()
        {
            return new Point(currentPosition.X - startPosition.X, currentPosition.Y - startPosition.Y);
        }

        private int GetOriginDeltaDistance()
        {
            Point p = GetOriginDelta();
            int x = p.X, y = p.Y;
            return (int)Math.Sqrt(x * x + y * y);
        }

        private int GetLastDeltaDistance()
        {
            Point p = GetLastDeltaMove();
            return (int)Math.Sqrt(p.X * p.X + p.Y * p.Y);
        }

        /// <summary>
        /// Updates statistical data while the mouse is beeing moved.
        /// </summary>
        private void UpdateStatistics(System.Windows.Forms.MouseEventArgs e, bool mouseDown)
        {
            int tick = Environment.TickCount;
            lastDuration = tick - lastMoveTick;
            lastMoveTick = tick;
            if (mouseDown)
            {
                // don't do this on a MouseUp event, otherwhise the dx/dy of last moving distance would always be 0/0!:
                moveDeltaPos.X = currentPosition.X;
                moveDeltaPos.Y = currentPosition.Y;
            }

            int x = Control.UnscaleX(e.X);
            int y = Control.UnscaleY(e.Y);
            currentPosition.X = x;
            currentPosition.Y = y;
            int dx = startPosition.X - x;
            int dy = startPosition.Y - y;
            int currentDistance = (int)Math.Sqrt((dx * dx + dy * dy));
            distance += currentDistance;
        }

        private bool ScrollDetected(System.Windows.Forms.MouseEventArgs e, out int dx, out int dy, out double speed)
        {
            Point p = GetLastDeltaMove();
            dx = Control.UnscaleX(p.X);
            dy = Control.UnscaleY(p.Y);
            int duration = lastDuration;
            speed = Math.Sqrt(dx * dx + dy * dy) / duration;
            return (speed > ScrollDetectionSpeedThreshold && duration < ScrollDetectionDurationThreshold);
        }

        private void OnMouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            isMouseDown = false;
            RemoveContextMenuDelay();
            StopSingleClickTimer();
            UpdateStatistics(e, false);
            bool mouseDown = isVirtualMouseDown;
            isVirtualMouseDown = false;
            this.mouseClickEventArgs.Handled = false;
            if (isDraggingOrScrolling)
            {
                int dx, dy;
                double speed;
                if (ScrollDetected(e, out dx, out dy, out speed))
                {
                    if (mouseDown) RaiseMouseUpEvent(false);
                    StartAutoScroll(dx, dy, speed);
                    return;
                }
                else
                {
                    if (mouseDown) RaiseMouseUpEvent(false);
                    StopAutoScroll();
                    mouseDown = false;
                    return;
                }
            }

            StopAutoScroll();
            isDraggingOrScrolling = false;
            if (mouseDown) RaiseMouseUpEvent(true);
        }

        /// <summary>
        /// Raise MouseUpEventArgs to controls.
        /// </summary>
        /// <param name="e">The original windows event.</param>
        /// <param name="cancel">Set to true to raise MouseClickEventArgs, otherwise false.</param>
        private void RaiseMouseUpEvent(bool raiseMouseClickEvents)
        {
            int duration = Environment.TickCount - mouseDownTime;

            MouseUpEventArgs ev = mouseUpEventArgs;
            ev.Reason = raiseMouseClickEvents ? MouseUpReason.ButtonUp : MouseUpReason.BeginScroll;
            ev.AllowDoubleClick = true;
            ev.Handled = false;
            ev.Distance = (int)distance;
            ev.DownPosition = startPosition;
            ev.UpPosition = currentPosition;
            ev.Duration = duration;
            RaiseMouseEvent(ev, (c, evt) => c.OnMouseUp(evt));
            if (raiseMouseClickEvents && !isDraggingOrScrolling)
            {
                HandleClickEvents(ev);
            }
            isVirtualMouseDown = false;

        }

        private void HandleClickEvents(MouseUpEventArgs ev)
        {
            if (ev.Duration > MaxDoubleClickPeriod) clickCount = 0;
            if (ev.Duration > ClickExpiredDuration) return;
            clickCount++;
            if (!ignoreMouseClick)
            {
                this.mouseClickEventArgs.Handled = false;
                this.mouseClickEventArgs.Position = ev.DownPosition;
                switch (clickCount)
                {
                    case 1:
                        if (ev.Duration >= MaxDoubleClickPeriod || !ev.AllowDoubleClick)
                        {
                            StopSingleClickTimer();
                            clickCount = 0;
                            RaiseMouseEvent(ev, (c, evt) => RaiseClickEvent(c, evt, MouseClickType.SingleClick));
                        }
                        else
                        {
                            StartSingleClickTimer();
                        }
                        break;

                    case 2:
                        StopSingleClickTimer();
                        clickCount = 0;
                        RaiseMouseEvent(ev, (c, evt) => RaiseClickEvent(c, evt, ev.Duration < MaxDoubleClickPeriod ? MouseClickType.DoubleClick : MouseClickType.SingleClick));
                        break;

                    default:
                        clickCount = 0;
                        StopSingleClickTimer();
                        break;

                }
            }
        }

        public void RaiseClickEvent(IMouseEventBehavior c, MouseUpEventArgs e, MouseClickType type)
        {
            if (e.ScreenBounds.Contains(e.UpPosition))
            {
                MouseClickEventArgs mce = mouseClickEventArgs;
                mce.MouseClickType = type;
                c.OnMouseClick(mce);
            }
        }

        private void OnMouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            Screen.Current.HideInputPanel();
            isMouseDown = true;
            StopSingleClickTimer();
            {
                // don't state IsMouseDown when there is a running scroll, but abort the scoll:
                isVirtualMouseDown = !isDraggingOrScrolling;
                //isMouseDown = true;
                StopAutoScroll();
                isDraggingOrScrolling = false;
            }

            int x = Control.UnscaleX(e.X);
            int y = Control.UnscaleY(e.Y);
            distance = 0;
            scrollEventArgs.Cancel = false;
            ignoreMouseMoves = false;
            startPosition.X = x;
            startPosition.Y = y;
            mouseDownTime = Environment.TickCount;

            MouseDownEventArgs ev = mouseDownEventArgs;
            ev.Position = startPosition;
            ev.Handled = false;

            DetermineTargetControl(x, y);
            // raise MouseDownEventArgs only, if no scroll has been aborted:
            if (isVirtualMouseDown)
            {
                Control mouseControl = RaiseMouseEvent(ev, (c, evt) => c.OnMouseDown(evt));
                HandleContextMenu(mouseControl);
            }
        }

        private void HandleContextMenu(Control mouseControl)
        {
            if (!isVirtualMouseDown) return;
            for (int i = mouseParentStack.Count - 1; i >= 0; i--)
            {

                Control c = mouseParentStack[i];
                IContextMenuBehavior contextMenu = c as IContextMenuBehavior;
                if (contextMenu != null && contextMenu.ContextMenu != null)
                {
                    ContextMenuControl = c;
                    contextMenuStartTime = Environment.TickCount;
                    Screen.SetContextMenuDelay(true, startPosition);
                    Screen.Tick -= OnDelayContextMenu;
                    Screen.Tick += OnDelayContextMenu;
                    break;
                }
            }
        }

        private void OnDelayContextMenu(object sender, TimeEventArgs e)
        {
            int elapsed = e.Tick - contextMenuStartTime;
            if (elapsed >= ContextMenuDelay)
            {
                Control control = ContextMenuControl;
                RemoveContextMenuDelay();
                RaiseMouseUpEvent(false);
                isVirtualMouseDown = false;
                MouseFocusControl = null;
                IContextMenuBehavior mc = (IContextMenuBehavior)control;
                if (mc.ContextMenu != null) mc.ContextMenu.Show(control);
            }
        }

        void RemoveContextMenuDelay()
        {
            Screen.SetContextMenuDelay(false, Point.Empty);
            Screen.Tick -= OnDelayContextMenu;
            ContextMenuControl = null;
        }

        /// <summary>
        /// Get all the controls from the lower to the upper which are under the specified location and place them in the list.
        /// </summary>
        private void DetermineTargetControl(int x, int y)
        {
            mouseParentStack.Clear();
            controlSize.Clear();
            Control mouseFocusControl = null;
            IList<Control> controls = Controls;

            List<Control> focusedControls = new List<Control>();
            if (controls != null)
            {
                int xOffset = 0;
                int yOffset = 0;

                mouseFocusControl = HitTestControlLoop(x, y, null, controls, xOffset, yOffset, focusedControls);
            }
            mouseFocusControl = mouseParentStack.Count > 0 ? mouseParentStack[mouseParentStack.Count - 1] : null;
            MouseFocusControl = mouseFocusControl;

            foreach (Control c in focusedControls)
            {
                IInputEventBehavior input = c as IInputEventBehavior;
                if (input != null && input.CanFocus)
                {
                    InputFocusControl = mouseFocusControl;
                    focusParentStack = focusedControls;
                    break;
                }
            }

        }

#if DEBUG
        void DumpControls(IEnumerable<Control> controls)
        {
            foreach (Control c in controls)
            {
                if (c.Visible)
                {
                    DumpControl(c);
                    Debug.IndentLevel++;
                    if (c.SubControls != null) DumpControls(c.SubControls);
                    Debug.IndentLevel--;
                }
            }
        }

        private static void DumpControl(Control c)
        {
            Debug.WriteLine(c.GetType().Name + ", " + c.Bounds.ToString());
        }

        public void DumpControls()
        {
            DumpControls(this.Controls);
        }

        public void DumpFocused()
        {
            foreach (Control c in focusParentStack) DumpControl(c);
        }

#endif

        private Control HitTestControlLoop(int x, int y, Control mouseFocusControl, IList<Control> controls, int xOffset, int yOffset, List<Control> focusedControls)
        {
            IList<Control> enumControls = controls;
            controls = null;
            for (int i = enumControls.Count - 1; i >= 0; i--)
            //for (int i = 0; i < enumControls.Count; i++)
            {
                Control c = enumControls[i];
                if (c.Visible)
                {
                    Rectangle r = new Rectangle(xOffset + c.X, yOffset + c.Y, c.Width, c.Height);
                    if (r.Contains(x, y))
                    {
                        bool isMouseControl = c is IMouseEventBehavior;
                        if (isMouseControl)
                        {
                            mouseFocusControl = c;
                            mouseParentStack.Add(c);
                            controlSize.Add(r);
                        }
                        if (c is IInputEventBehavior)
                        {
                            focusedControls.Add(c);
                        }
                        if (c.SubControls != null)
                        {
                            // only check the child item if the hit test is also within the margin of the control:
                            Margin m = c.Clipping;
                            Rectangle rcmp = new Rectangle(xOffset + c.X + m.LeftZero, yOffset + c.Y + m.TopZero, c.Width - m.Width, c.Height - m.Height);
                            if (rcmp.Contains(x, y))
                            {
                                HitTestControlLoop(x, y, mouseFocusControl, c.SubControls, r.X, r.Y, focusedControls);
                            }
                        }
                        if ((c.Flags & ControlFlags.DisableHitTest) == 0) break;
                    }
                }
            }
            return mouseFocusControl;
        }

        delegate void RaiseInputEventFunc(IInputEventBehavior c);
        delegate void RaiseMouseEventFunc<T>(IMouseEventBehavior c, T e) where T : MouseEventArgs;
        delegate void RaiseScrollEventFunc<T>(IScrollEventBehavior c, T e) where T : EventArgs;

        private Control RaiseMouseEvent<T>(T e, RaiseMouseEventFunc<T> func) where T : MouseEventArgs
        {
            Control mouseControl = null;
            e.Cancel = false;

            // it's possible that the focused list changes while raising methods, therfore create a copy before:
            Control[] list = mouseParentStack.ToArray();
            for (int i = list.Length - 1; i >= 0; i--)
            {
                Control c = list[i];
                if (c.Visible)
                {
                    e.ScreenBounds = GetControlsScreenBounds(i);
                    if (c is IMouseEventBehavior) func((IMouseEventBehavior)c, e);
                    if (e.Handled && mouseControl == null)
                    {
                        mouseControl = c;
                    }
                    if (e.Cancel) break;
                }
            }
            return mouseControl;
        }

        private void RaiseScrollEvent<T>(T e, RaiseScrollEventFunc<T> func) where T : HandledEventArgs
        {
            for (int i = mouseParentStack.Count - 1; i >= 0; i--)
            {
                Control c = mouseParentStack[i];
                if (c.VisibleAndEnabled)
                {
                    IScrollEventBehavior seb = c as IScrollEventBehavior;
                    ScrollEventArgs se = e as ScrollEventArgs;
                    if (se != null) se.ScreenBounds = GetControlsScreenBounds(i);
                    if (seb != null)
                    {
                        func(seb, e);
                        if (e.Cancel) break;
                    }
                }
            }
        }

        private Rectangle GetControlsScreenBounds(int index)
        {
            if (index >= 0 && index < controlSize.Count)
            {
                return controlSize[index];
            }
            else return Rectangle.Empty;
        }

        void StopAutoScroll()
        {
            Screen.Current.Tick -= OnScroll;
            if (isDraggingOrScrolling) RaiseEndScrollEvent();
        }

        void StartAutoScroll(int scrollDx, int scrollDy, double speed)
        {
            float duration = lastDuration;
            scrollData.Reset(currentPosition.X, currentPosition.Y, scrollDx / duration * 2, scrollDy / duration * 2);
            Screen.Current.Tick += OnScroll;
        }

        protected virtual void OnScroll(object sender, EventArgs e)
        {
            if (isDraggingOrScrolling)
            {
                if (!scrollData.Step())
                {
                    StopAutoScroll();
                }
                else
                {

                    ScrollEventArgs ev = scrollEventArgs;
                    currentPosition.Y = scrollData.Y;
                    currentPosition.X = scrollData.X;
                    ev.IsMouseDown = false;

                    DetermineScrollOrBegingScrollEvents();
                    //  Screen.Current.Invalidate(null);
                }

            }
            else
            {
                StopAutoScroll();
            }
        }

        #endregion
        #region Keyboard methods

        private IInputEventBehavior GetInputControl()
        {
            return InputFocusControl as IInputEventBehavior;
        }

        public void OnKeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
        {
            RaiseInputEvent(e, (c) => c.OnKeyPress(e));
        }

        public void OnKeyUp(object sender, System.Windows.Forms.KeyEventArgs e)
        {
            RaiseInputEvent(e, (c) => c.OnKeyUp(e));

        }

        public void OnKeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
        {
            StopAutoScroll();
            RaiseInputEvent(e, (c) => c.OnKeyDown(e));

        }

        /// <summary>
        /// Simulates a pressed key.
        /// </summary>
        /// <param name="key">The key to simulate.</param>
        public void KeyPress(char key)
        {
            System.Windows.Forms.KeyPressEventArgs e = new System.Windows.Forms.KeyPressEventArgs(key);
            OnKeyPress(this, e);
        }

        private void RaiseInputEvent<T>(T e, RaiseInputEventFunc func) where T : EventArgs
        {
            Control[] focused = focusParentStack.ToArray();
            for (int i = focused.Length - 1; i >= 0; i--)
            {
                Control c = focused[i];
                IInputEventBehavior ieb = c as IInputEventBehavior;
                if (ieb != null)
                {
                    func(ieb);
                }
            }
        }


        #endregion
        #region Methods


        /// <summary>
        /// sets the focus to the specified control.
        /// </summary>
        /// <param name="control">The control to set the focus to.</param>
        /// <returns>True if the control has got the focus, otherwhise false (which means that it is either not in the tree or not enabled or visible).</returns>
        public bool Focus(Control control)
        {
            if (control == null)
            {
                focusParentStack.Clear();
                InputFocusControl = null;
            }
            else
            {
                if (InputFocusControl != control)
                {
                    bool focused = Focus(Controls, control, focusParentStack);

                    if (focused)
                    {
                        MouseFocusControl = null;
                        InputFocusControl = control;
                    }
                    return focused;

                }
            }
            return true;
        }

        public void HideToolTip()
        {
            if (toolTipControl != null)
            {
                IToolTip toolTip = (IToolTip)toolTipControl;
                toolTip.ToolTip.Visible = false;
                toolTipControl.VisibleChanged -= ToolTipVisibleOrEnabled;
                toolTipControl.EnabledChanged -= ToolTipVisibleOrEnabled;
                toolTipControl = null;
            }
        }

        protected virtual void OnInputFocusChanged(Control oldValue, Control newValue)
        {
            HideToolTip();

            if (InputFocusChanged != null) InputFocusChanged(this, EventArgs.Empty);

            CheckShowToolTip(newValue);

        }

        private void CheckShowToolTip(Control newValue)
        {
            IToolTip toolTip = newValue as IToolTip;
            if (toolTip != null && toolTip.ToolTip != null)
            {
                toolTipControl = newValue;
                if (newValue.VisibleAndEnabled && toolTip.ToolTip.Enabled)
                {
                    newValue.VisibleChanged += ToolTipVisibleOrEnabled;
                    newValue.EnabledChanged += ToolTipVisibleOrEnabled;
                    toolTip.ToolTip.Show(newValue);
                }
            }
        }

        void ToolTipVisibleOrEnabled(object sender, EventArgs e)
        {
            Control c = (Control)sender;
            IToolTip toolTip = (IToolTip)sender;
            toolTip.ToolTip.Visible = (c.Enabled && c.Visible);
        }

        protected virtual void OnMouseFocusChanged()
        {
            if (MouseFocusChanged != null) MouseFocusChanged(this, EventArgs.Empty);
        }

        private bool Focus(IList<Control> controls, Control controlToFocus, IList<Control> controlStack)
        {
            foreach (Control c in controls)
            {
                if (!c.VisibleAndEnabled) continue;

                if (c == controlToFocus)
                {
                    controlStack.Add(c);
                    return true;
                }

                if (c.SubControls != null)
                {
                    if (Focus(c.SubControls, controlToFocus, controlStack))
                    {
                        controlStack.Insert(0, c);
                        return true;
                    }
                }
            }
            return false;
        }

        /// <summary>
        /// Gets whether either the specified control or one of its children has the focus.
        /// </summary>
        internal bool HasFocus(Control control)
        {
            foreach (Control c in mouseParentStack)
            {
                if (c == control) return true;
            }
            return false;
        }

        #endregion
        #region events

        public event EventHandler InputFocusChanged;
        public event EventHandler MouseFocusChanged;


        #endregion
    }
}
